﻿using Abp.Runtime.Session;
using Abp.Specifications;
using Abp.Timing;
using DotNetCore.CAP;
using FluentFTP;
using Hicap.QRCodeWarehouse.Core.Common;
using Hicap.QRCodeWarehouse.Core.EFCore;
using Hicap.QRCodeWarehouse.Core.Models.DbSet;
using Hicap.QRCodeWarehouse.Core.Models.Other;
using Hicap.QRCodeWarehouse.Core.Models.Transfer;
using Hicap.QRCodeWarehouse.Core.Notify;
using Microsoft.EntityFrameworkCore.DynamicLinq;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Threading.Tasks;

namespace Hicap.QRCodeWarehouse.Core.Managers
{
    public class FileRepositoryManager : ManagerBase<FileRepository>, IFileRepositoryManager
    {
        private readonly NotifyHub _notifyHub;
        private readonly FtpClient _ftpClient;
        private readonly IAbpSession _session;
        private readonly IProductManager _productManager;
        private readonly ICapPublisher _capBus;
        private readonly IConfiguration _configuration;

        public FileRepositoryManager(IUnitOfWork unitOfWork, NotifyHub notifyHub, FtpClient ftpClient, IAbpSession session, IProductManager productManager, ICapPublisher capBus, IConfiguration configuration) : base(unitOfWork)
        {
            _notifyHub = notifyHub;
            _ftpClient = ftpClient;
            _session = session;
            _productManager = productManager;
            _capBus = capBus;
            _configuration = configuration;

            ForeignKeys.Add(x => x.Product);

            QueryCondition.Add(x => x.IsDeleted == false);

            if (_session.UserId.HasValue)
                QueryCondition.Add(x => x.UserId == _session.UserId.Value || HaveAllowDownloadUsers(x.AllowDownloadUsers));

            //            CreateActions.Add(x => x.UserId = Convert.ToInt32(_session.UserId));
            CreateActions.Add(x => x.CreationTime = Clock.Now);
            CreateActions.Add(x => x.IsDeleted = false);
        }

        public async Task MakeDataFile(TakeFileCommandDto cmd)
        {
            if (_session.UserId != null && _session.UserId != 0) cmd.UserId = _session.UserId.Value;
            await _capBus.PublishAsync(MessageQueueChanelConst.CodeTakenServiceAction, cmd, MessageQueueChanelConst.CodeTakenServiceAction + ".End");
        }

        [CapSubscribe(MessageQueueChanelConst.CodeTakenServiceAction + ".End", Group = SysConsts.SysName)]
        public void FinalFeedBackMessage(ActionResult result)
        {
            Task.Run(async () =>
            {
                await _notifyHub.SendToGroup(NotificationMethod.TakenFileFeedBack + ".End", JsonConvert.SerializeObject(result));
            });

            if (result.IsSuccessed)
                Log.Information($"FeedBack Finished:{MessageQueueChanelConst.CodeTakenServiceAction}--{result}");
            else
                Log.Error($"FeedBack have a Error:{MessageQueueChanelConst.CodeTakenServiceAction}--{result}");
        }

        private bool HaveAllowDownloadUsers(string allowDownloadUsers)
        {
            if (string.IsNullOrEmpty(allowDownloadUsers)) return false;

            string[] list = allowDownloadUsers.Split(",");

            return list.Any(x => _session.UserId != null && _session.UserId.Value.ToString().Equals(x));
        }

        public IQueryable<FileRepository> GetQueryWithRecycleBin(Expression<Func<FileRepository, bool>> predicate)
        {
            IQueryable<FileRepository> query = Repository.GetIncludingQuery(ForeignKeys.ToArray());

            Expression<Func<FileRepository, bool>> expression = x =>
                x.UserId == _session.UserId.Value ||
                HaveAllowDownloadUsers(x.AllowDownloadUsers);

            expression = expression.And(x => x.IsDeleted == true);

            return query.Where(expression);
        }

        public async Task RestoreWithRecycleBin(int input, bool isBatch = false)
        {
            FileRepository entity = await Repository.GetAsync(input);
            entity.IsDeleted = false;
            await InsertOrUpdate(entity);
            if (!isBatch)
                await UnitOfWork.SaveChangesAsync();
        }

        public async Task BatchRestoreWithRecycleBin(List<int> input)
        {
            await Task.Run(async () =>
            {
                input.ForEach(async x => { await RestoreWithRecycleBin(x, true); });
                await UnitOfWork.SaveChangesAsync();
            });
        }

        public async Task DeleteWithRecycleBin(int input, bool isBatch = false)
        {
            FileRepository entity = await Repository.GetAsync(input);
            Repository.Delete(entity, false);

            if (!isBatch)
                await UnitOfWork.SaveChangesAsync();
            await _ftpClient.DeleteFileAsync(entity.Path);
        }

        public async Task BatchDeleteWithRecycleBin(List<int> input)
        {
            await Task.Run(async () =>
            {
                input.ForEach(async x => { await DeleteWithRecycleBin(x, true); });
                await UnitOfWork.SaveChangesAsync();
            });
        }

        public async Task Download(int id, Stream outStream)
        {
            FileRepository fileInfo = await GetQuery(x => x.Id == id).FirstOrDefaultAsync();
            if (fileInfo != null)
            {
                if (await _ftpClient.DownloadAsync(outStream, fileInfo.Path))
                {
                    fileInfo.DownloadTimes += 1;
                    fileInfo.LastDownloadDateTime = Clock.Now;
                    await Update(fileInfo);
                }
            }
        }

        public async Task ImportDataFile(ImportFileCommandDto cmd)
        {
            if (string.IsNullOrEmpty(cmd.FilePath))
            {
                cmd.FilePath = (await GetQuery(x => x.Id == cmd.FileRepositoryId).FirstOrDefaultAsync()).FilePath;
            }

            await _capBus.PublishAsync(MessageQueueChanelConst.CodeGenServiceImportAction, cmd, MessageQueueChanelConst.CodeGenServiceImportAction + ".End");
        }

        [CapSubscribe(MessageQueueChanelConst.CodeGenServiceImportAction + ".End", Group = SysConsts.SysName)]
        public void FinalImportFeedBackMessage(ActionResult result)
        {
            Task.Run(async () =>
            {
                await _notifyHub.SendToGroup(NotificationMethod.ImportFeedBack + ".End", JsonConvert.SerializeObject(result));
            });

            if (result.IsSuccessed)
                Log.Information($"FeedBack Finished:{MessageQueueChanelConst.CodeGenServiceImportAction}--{result}");
            else
                Log.Error($"FeedBack have a Error:{MessageQueueChanelConst.CodeGenServiceImportAction}--{result}");
        }

        public async Task Upload(FileRepository input, Stream inStream)
        {
            string ftpDir = $"Upload/{_session.UserId}/{Clock.Now:yyyy-MM-dd-HH}";
            string ftpPath = $"{ftpDir}/{input.FileName}.zip";

            if (_ftpClient.Credentials == null)
                _ftpClient.Credentials = new NetworkCredential(_configuration["DefaultAuthorizationUser:UserName"], _configuration["DefaultAuthorizationUser:Password"]);

            if (!_ftpClient.DirectoryExists(ftpDir))
                _ftpClient.CreateDirectory(ftpDir);

            if (_ftpClient.Upload(inStream, ftpPath, FtpExists.Overwrite, true))
            {
                input.Path = ftpPath;
                input.FileSourceType = FileSourceTypeEnum.Upload;
                input.FileType = FileTypeEnum.TextData;
                input.CreationTime = Clock.Now;
                input.UserId = _session.UserId;
                input.Size = inStream.Length;

                input.IsDeleted = false;
                input.DownloadTimes = 0;
                input.RecordsCount = -1;

                int id = await InsertAndGetId(input);
                Log.Information($"File Uploaded Succeed : [{id}]");
            }
        }

        [CapSubscribe(MessageQueueChanelConst.CodeGenServiceImportFeedBack, Group = SysConsts.SysName)]
        public async Task<ActionResult> DispatchCodeImportFeedback(ImportFileFeedbackDto data)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            try
            {
                await _notifyHub.SendToGroup(NotificationMethod.ImportFeedBack, JsonConvert.SerializeObject(data));

                if (data.Progress == 1)
                {
                    Product product = await _productManager.GetQuery(x => x.Id == data.ProductId).FirstOrDefaultAsync();
                    if (data.GenerationRuleType == GenerationRuleType.Inner)
                    {
                        if (product != null) product.TotoalInnerCount += data.TotalCount;
                    }
                    else
                    {
                        if (product != null) product.TotoalOuterCount += data.TotalCount;
                    }

                    await _productManager.Update(product);
                }

                return new ActionResult(MessageQueueChanelConst.CodeGenServiceImportFeedBack, true,
                    watch.ElapsedMilliseconds, Clock.Now, data);
            }
            catch (Exception e)
            {
                return new ActionResult(MessageQueueChanelConst.CodeGenServiceImportFeedBack, false, watch.ElapsedMilliseconds, Clock.Now, data, e);
            }
        }

        [CapSubscribe(MessageQueueChanelConst.CodeTakenServiceFeedBack, Group = SysConsts.SysName)]
        public async Task<ActionResult> DispatchCodeTakenFeedback(TakeFileFeedbackDto data)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            try
            {
                await _notifyHub.SendToGroup(NotificationMethod.TakenFileFeedBack, JsonConvert.SerializeObject(data));

                if (data.Status == GenerationStatus.Complete)
                {
                    FileRepository newEntity = new FileRepository()
                    {
                        FileName = data.FileName,
                        Size = data.ResultFile.FileSize,
                        RecordsCount = data.ResultFile.FileRecoredCount,
                        DownloadTimes = 0,
                        Path = data.ResultFile.FilePath,
                        FileSourceType = FileSourceTypeEnum.Download,
                        FileType = FileTypeEnum.TextData,
                        IsInnerTypeBePrimary = true,
                        UserId = data.UserId,
                        ProductId = data.ProductId,
                        GenerationRuleType = data.GenerationRuleType
                    };

                    int id = await InsertAndGetId(newEntity, new List<Action<FileRepository>>()
                        {
                            x => x.UserId = data.UserId,
                            x => x.CreationTime = Clock.Now
                        });
                    Log.Information($"File Saved Succeed : [{id}]");

                    Product product = await _productManager.GetQuery(x => x.Id == data.ProductId).FirstOrDefaultAsync();
                    if (data.GenerationRuleType == (int)GenerationRuleType.Inner)
                    {
                        if (product != null) product.TotoalDownloadInnerCount = data.TotalDownload;
                    }
                    else
                    {
                        if (product != null) product.TotoalDownloadOuterCount = data.TotalDownload;
                    }

                    await _productManager.Update(product, new List<Action<Product>>()
                        {
                            x => x.LastModifierUserId = data.UserId,
                            x => x.LastModificationTime = Clock.Now
                        });
                }

                return new ActionResult(MessageQueueChanelConst.CodeTakenServiceFeedBack, true, watch.ElapsedMilliseconds, Clock.Now, data);
            }
            catch (Exception e)
            {
                return new ActionResult(MessageQueueChanelConst.CodeTakenServiceFeedBack, false, watch.ElapsedMilliseconds, Clock.Now, data, e);
            }
        }
    }
}